#include <p18cxxx.h>
#include "define.h"
#include <delays.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
 
extern char recvbuf[];
extern char* recvbuf_iPtr;
extern char* recvbuf_ePtr;
 
extern const rom unsigned char _intro[];
extern const rom unsigned char _ul[];
extern const rom unsigned char _ur[];
extern const rom unsigned char _fy[];
extern const rom unsigned char _wh[];
extern const rom unsigned char _ct[];
extern const rom unsigned char _te[];
extern const rom unsigned char _ce[];
extern const rom unsigned char _rw[];
extern const rom unsigned char _ss[];
extern const rom unsigned char _sr[];
extern const rom unsigned char _cs[];
extern const rom unsigned char _outro[];
extern const rom unsigned char _fs[];
 
extern const rom unsigned char _raw1[];
extern const rom unsigned char _currwh[];
extern const rom unsigned char _dev1[];
extern const rom unsigned char _dev2[];
extern const rom unsigned char _gs[];
extern const rom unsigned char _os[];
 
extern const rom unsigned char _11[];
extern const rom unsigned char _12[];
extern const rom unsigned char _13[];
extern const rom unsigned char _14[];
 
extern const rom unsigned char _key[];
extern const rom unsigned char _enter[];
 
extern const rom unsigned char _ts[];
 
extern const rom unsigned char _cr[];
extern const rom unsigned char _lf[];
 
extern const rom unsigned char _count1[];
extern const rom unsigned char _count1b[];
extern const rom unsigned char _count2[];
extern const rom unsigned char _count3[];
extern const rom unsigned char _count4[];
extern const rom unsigned char _count5[];
 
extern const rom unsigned char _calib1[];
extern const rom unsigned char _calib2[];
extern const rom unsigned char _calib3[];
extern const rom unsigned char _calib4[];
 
extern const rom unsigned char _colon[];
 
extern const rom unsigned char _LCDmenu[];
extern const rom unsigned char _LCDcurrwh[];
extern const rom unsigned char _LCDgs[];
extern const rom unsigned char _LCDos[];
 
extern const rom unsigned char _LCDcount1[];
extern const rom unsigned char _LCDcount2[];
extern const rom unsigned char _LCDcount3[];
extern const rom unsigned char _LCDcount4[];
extern const rom unsigned char _LCDback[];
 
extern const rom unsigned char _speakCount4[];
 
extern const rom unsigned char _fac1[];
 
extern unsigned char unit;    /** Current mass unit, GRAMS or OUNCES. */
unsigned char mode = LOCAL;     /** Current mode: LOCAL, REMOTE, or FACTORY. */
unsigned char modeOld = ' ';    /** Stores the previous mode. */
unsigned char func = WEIGH;     /** Current function: WEIGH, COUNT, CALIBRATE, etc. */
unsigned char funcOld = ' ';    /** Stores the previous function. */
unsigned char phase = 0;    /** The phase is a subsection of func, used for stepping through COUNT and CALIBRATE. */
extern int mass;        /** The current mass, in a GRAMS or OUNCES as specified by unit. */
extern int varMass;        /** Current variance mass. */
extern int tareMass;        /** Current tare mass. */
extern int rawMass;        /** Current raw mass. */
int lastMass = 0x7FFF;        /** Stores the previous mass. Initialised value used to assure a change upon first reading. */
unsigned char msg[10] = "";    /** A temporary variable for storing the mass as a string, for output. */
unsigned char speak_str[35] = ""; /** A temporary variable for storing the output for the TTS. */
unsigned char ok = 0;        /** If the user hits enter or presses okay, this is set. */
unsigned char back = 0;        /** If the user hits escape or presses back, this is set. */
unsigned char absMode = LOCAL;    /** This is an absolute storage of whether we are in LOCAL or REMOTE (and only these options) */
 
int countSet1Mass = 0;        /** Temporary variable for storing first count mass value. */
int countSet2Mass = 0;        /** Temporary variable for storing second count mass value. */
int countItems = 0;        /** Temporary variable for storing count item number. */
int countItems2 = 0;        /** Temporary variable for storing count item number. */
float countMassPerItem = 0;    /** Temporary variable for storing count mass per item. Float for accuracy. */
unsigned char stringItems[6];    /** Temporary variable for storing the string of the number of items in count. */
unsigned char numInput = FALSE; /** If TRUE, numerical input are treated as such, rather than being menu options. */
unsigned char numbuf[5];    /** Stores up to 4 digits of numerical input, plus space for termination character. */
unsigned char numbufi = 0;    /** Index for numbuf. */
unsigned char mute = 0;        /** Stores if the user has muted/unmuted the system. */
 
void clearScreen(void);
unsigned char parseMode(unsigned char iChar);
unsigned char parseFunc(unsigned char iChar);
void printInstructions(char mode, char func);
void generateLine1(char key, char* function);
void generateLine2(char key, char* function);
extern unsigned char* get_rom_string(const rom unsigned char* txPtr);
extern void transmit(unsigned char* message);
void redrawStatus(void);
void phaseStates(void);
extern void setTare(void);
extern void int_to_string(int rawNumber, unsigned char* string);
void drawMenuLCD(void);
extern void string_LCD(unsigned char* pointer);
extern void get_weight_line(int weight, char* unit, char* output);
extern void tts_send_string(char* command, char* output);
extern void speak(char* outstring);
extern void tts_reset(void);
extern char tts_is_ready(void);
extern void calibrate(unsigned int zeroVoltage, unsigned int secondVoltage, int secondMass, char massUnit);
 
extern unsigned int getVoltage(void);
void calibrate_mode(void);
void count_mode(void);
 
/**
 * Checks input characters from the recieve buffer, and modifies the state of the program based
 * on this input. The phaseStates function is then called, evaluating the implications of any
 * changes.
 *
 * Important globals modified: func, mode, phase
 * @pre The LCD, serial, and TTS are all initialized
 * @post LCD, serial, and/or TTS output. The tare may be modified.
 */
void checkState (void)
{
    unsigned char rcb = *recvbuf_ePtr;
    Delay10KTCYx(1);    //Delay to fix buffer problems
 
    if((recvbuf_ePtr != recvbuf_iPtr))    //If there's a new character in the receive buffer
    {
        rcb = *recvbuf_ePtr;        //Get the character
 
        // Keypad handling - convert into char (as if serial input) to be handled by rest of routine
        if((rcb >= '0') && (rcb <= '9') ) //This is a number input from the keypad
        {
            if(numInput)        //If we treat it as numerical input
            {
                numbuf[numbufi] = rcb;
                numbufi++;
                if (numbufi == 3)
                {
                    ok = 1; //maximum 4 digits in number
                }
            rcb = 0;
            }
            else    //Keypad input as menu selection
            {
                switch(rcb)
                {
                    case '1':
                    rcb = WEIGH;
                    break;
 
                    case '2':
                    rcb = COUNT;
                    break;
 
                    case '3':
                    rcb = TARE;
                    break;
 
                    case '4':
                    rcb = CHANGEUNIT;
                    break;
 
                    case '7':
                    rcb = LOCAL;
                    break;
 
                    case '8':
                    mute = 1 - mute;
                    break;
 
                    case '9':
                    //Respeak
                        mute = 0;
                        if(func == WEIGH)    //If number, recalculate mass stuff
                        {
                            int_to_string(mass, msg);
                            string_LCD(get_rom_string(_LCDcurrwh));
                            string_LCD(msg);
                            strcpy(speak_str,msg);
 
                            if (unit == GRAMS)
                            {
                                strcatpgm2ram(speak_str,_gs);
                            }
                            else
                            {
                                strcatpgm2ram(speak_str,_os);
                            }
                        }
 
                        if(tts_is_ready())
                        {
                            speak(speak_str);
                        }
                        rcb = 0;
                    break;
 
 
                }
            }
        }
 
 
 
        // Reset flags
        ok = 0;
        back = 0;
 
        //Need to detect special characters and input number, but I'm not sure how to store it all yet.
        //A small buffer is needed for the numbers.
        if(rcb == '\r' || rcb == '#')    //If carriage return or OK
        {
            rcb = OK;
            ok = 1;
        }
        if(rcb == BACK || rcb == '\b' || rcb == 27) //If back, backspace or escape
        {
            rcb = BACK;
            back = 1;
        }
 
        mode = parseMode(rcb);        // Check for a valid mode character.
        if (!mode)
        {
            mode = modeOld;
        }
 
        func = parseFunc(rcb);        // Check for a valid function character.
 
        if (!func)
        {
            func = funcOld;
        }
 
        if ((mode != FACTORY) && (func >=65) && (func <= 90))    // Factory functions allowed only in factory mode.
        {
            //Between 65 and 90 are capital letters, so factory function characters must be capitals
            func = funcOld;
            if((func >=65) && (func <= 90))
            {
                func = WEIGH;
            }
        }
 
        if(mode != modeOld || func != funcOld)    //If the state has changed
        {
            phase = 0;    //Reset the phase
            lastMass = 0x7FFF;
        }
 
        if (rcb == CHANGEUNIT)
        {
            unit = !unit;
        }
 
        rcb = 0;
        recvbuf_ePtr++;
 
        if (recvbuf_ePtr >= recvbuf + I_BUFSIZE)
        {
            recvbuf_ePtr = recvbuf;
        }
    }
 
    //Set absolute mode setting
    if(mode != modeOld)
    {
        if(mode == REMOTE || mode == FACTORY)
        {
            absMode = REMOTE;
            func = WEIGH;
        }
        if(mode == LOCAL)
        {
            absMode = LOCAL;
            func = MENU;
        }
        redrawStatus();
    }
 
    phaseStates();    //Run through main logic
 
    modeOld = mode;
    funcOld = func;
}
 
/**
 * Calls the relevant functions to clear the screen on the terminal and redraw the menu.
 * @pre The serial must be initialised
 * @post See clearScreen and printInstructions in Scales.c
 */
void redrawStatus(void)
{
    if(absMode == LOCAL)    //Since this function is for serial only, quit if we're not using serial
    {
        return;
    }
    clearScreen();
    printInstructions(mode, func);
}
 
/**
 * Evaluates the effect of the func and mode variables, which may have been modified in the checkState
 * function.
 *
 * This method will output menus and displays to the serial, LCD, and TTS where appropriate, based on
 * the current state of the program.
 *
 * The phase variable is used extensively in the functions Count and Calibrate to step through the
 * individual states of the functions. This method will, based in received input, choose when to
 * move forwards, and will, when complete, return to the main menu or weigh function, where appropriate.
 *
 * Important globals modified: func, mode, phase
 * @pre LCD, serial, and TTS are initialised
 * @post LCD, serial, and/or TTS output where appropriate. Calibration and Tare may be performed.
 */
void phaseStates(void)
{
    if(back)
    {
        if(mode == LOCAL)
        {
            func = MENU;
            phase = 0;
        }
        else
        {
            func = WEIGH;
            phase = 0;
        }
        back = 0;
    }
 
    switch (func)
    {
        case MENU:    //Should be LCD only
            if(func != funcOld)
            {
                drawMenuLCD();
            }
            break;
        case WEIGH:
            if(mass != lastMass)
            {
                int_to_string(mass, msg);
 
 
                if(absMode == REMOTE)
                {
                    redrawStatus();
 
                    transmit(get_rom_string(_currwh));
                    transmit(msg);
 
 
                    if (unit == GRAMS)
                    {
                        transmit(get_rom_string(_gs));
                    }
                    else
                    {
                        transmit(get_rom_string(_os));
                    }
                    transmit(get_rom_string(_outro));
                }
                else
                {
                    string_LCD(get_rom_string(_LCDcurrwh));
                    string_LCD(msg);
                    strcpy(speak_str,msg);
 
 
                    if (unit == GRAMS)
                    {
                        string_LCD(get_rom_string(_LCDgs));
                        strcatpgm2ram(speak_str,_gs);
 
                    }
                    else
                    {
                        string_LCD(get_rom_string(_LCDos));
                        strcatpgm2ram(speak_str,_os);
                    }
                    string_LCD(get_rom_string(_LCDback));
 
                    if(tts_is_ready() && !mute)
                    {
                        speak(speak_str);
                    }
                }
                lastMass = mass;
            }
            break;
 
        case COUNT:
            void count_mode(void);
            break;
 
        case TARE:
            setTare();
            func = WEIGH;
            phase = 0;
            break;
 
        case CALIBRATE:            //Should never happen in local mode, so not designed to cope with LCD
            calibrate_mode(/*mode, func, phase, ok, unit*/);
            break;
        case STATS:
 
            if(mass != lastMass)
            {
                int_to_string(varMass, msg);
 
 
                if(absMode == REMOTE)
                {
                    redrawStatus();
                    transmit(get_rom_string(_dev2));
                    transmit(get_rom_string(_outro));
 
                    transmit(get_rom_string(_dev1));
                    transmit(msg);
 
 
                    if (unit == GRAMS)
                    {
                        transmit(get_rom_string(_gs));
                    }
                    else
                    {
                        transmit(get_rom_string(_os));
                    }
                    transmit(get_rom_string(_outro));
                }
                lastMass = mass;
            }
            break;
 
        case RAW:
            if(mass != lastMass)
            {
                rawMass = mass + tareMass;
                int_to_string(rawMass, msg);
 
 
                if(absMode == REMOTE)
                {
                    redrawStatus();
 
                    transmit(get_rom_string(_raw1));
                    transmit(msg);
 
 
                    if (unit == GRAMS)
                    {
                        transmit(get_rom_string(_gs));
                    }
                    else
                    {
                        transmit(get_rom_string(_os));
                    }
                    transmit(get_rom_string(_outro));
                }
                lastMass = mass;
            }
            break;
 
        case SAMPLES:
            //Not written yet
            break;
    }
}
 
/**
 * Draws the main menu on the LCD. This menu shows the main options available to
 * the user.
 * @pre LCD is initialised
 * @post The menu is added to the LCD buffer
 */
void drawMenuLCD(void)
{
    if(absMode == REMOTE)
    {
        return;
    }
    string_LCD(get_rom_string(_LCDmenu));
    if (unit == GRAMS)    //Append !unit
    {
        string_LCD(get_rom_string(_os));
    }
    else
    {
        string_LCD(get_rom_string(_gs));
    }
 
}
 
/**
 * To be called strictly by phaseStates only, under case CALIBRATE.
 * Uses the phase variable to move between stages in the calibrate
 * state.
 *
 * See calibrate in scales.c for further postcondition and global variable
 * information.
 *
 * Important globals modified: phase, func.
 * @pre func = CALIBRATE, mode = FACTORY, serial initialised
 * @post Phase moves on, or calibration is performed and finishes output to serial.
 */
void calibrate_mode(void)
{
    static unsigned int v1, v2, m1, m2;
    unsigned char calibrationString[16];
            switch(phase)
            {
                case 0:
                    //First prompt: Unload scales and press enter
                    redrawStatus();
                    transmit(get_rom_string(_calib1));
                    transmit(get_rom_string(_enter));
                    transmit(get_rom_string(_outro));
                    phase = 1;
                    ok = 0;
                    break;
                case 1:
                    //Wait until OK and store voltage
                    if(ok)
                    {
                        v1 = getVoltage();
                        m1 = 0;
                        ok = 0;
                        phase = 2;
                    }
                    break;
                case 2:
                    //Second prompt
                    //Ask user to put some known quantity on the scales
                    redrawStatus();
                    transmit(get_rom_string(_calib2));
                    transmit(get_rom_string(_enter));
                    transmit(get_rom_string(_outro));
                    ok = 0;
                    phase = 3;
                case 3:
                    //Wait until OK and store mass
                    if(ok)
                    {
                        v2 = getVoltage();
                        ok = 0;
                        phase = 4;
                    }
                    break;
                case 4:
                    //Third prompt
                    //Ask user to specify how much is on the scales in the working unit
                    redrawStatus();
                    transmit(get_rom_string(_calib3));
                    if (unit == GRAMS)
                    {
                        transmit(get_rom_string(_gs));
                    }
                    else
                    {
                        transmit(get_rom_string(_os));
                    }
                    transmit(get_rom_string(_colon));
                    transmit(get_rom_string(_outro));
                    phase = 5;
                case 5:
                    //Wait until OK and store value
                    numInput = TRUE; //Allow the user to input numbers
                    if(ok)
                    {
                        numbuf[numbufi] = '\0';
                        transmit(numbuf);
                        m2 = atoi(numbuf); //must fix to accept user input
                        numInput = FALSE;
                        ok = 0;
                        phase = 6;
                    }
                    break;
                case 6:
                    //Fourth prompt
                    redrawStatus();
                    transmit(get_rom_string(_calib4));
                    transmit(get_rom_string(_calib1));
                    transmit(get_rom_string(_enter));
                    transmit(get_rom_string(_outro));
                    //sprintf(calibrationString,"v1 = %d, m1 = %d, v2 = %d, m2 = %d\n",v1,m1,v2,m2);
                    //transmit(calibrationString);
                    //Delay10KTCYx(10);
 
                    phase = 7;
                    break;
                case 7:
                    //Wait for OK, then back to main menu
                    calibrate(v1, v2, m2, unit);
                //    setTare();
                    if(ok)
                    {
                        ok = 0;
                        func = WEIGH;
                        phase = 0;
                    }
                    break;
            }
}
 
/**
 * To be called strictly by phaseStates only, under case COUNT.
 * Uses the phase variable to move between stages in the count state.
 * If count is performed, the results will be displayed.
 * Output to serial, or LCD and TTS.
 * Important globals modified: phase, func.
 * @pre func = COUNT, and serial, TTS, and LCD initialised
 * @post Phase moves on, or count is performed and finished.
 */
void count_mode(void)
{
    switch(phase)
    {
        case 0:
            //First prompt
            //Please load a number of identical items....
            if(absMode == REMOTE)
            {
                redrawStatus();
                transmit(get_rom_string(_count1));
                transmit(get_rom_string(_enter));
                transmit(get_rom_string(_outro));
            }
            else
            {
                string_LCD(get_rom_string(_LCDcount1));
                if(tts_is_ready() && !mute)
                {
                    strcpy(speak_str,get_rom_string(_LCDcount1));
                    speak(speak_str);
                }
            }
            phase = 1; //String has been transmitted - wait for user to confirm
            break;
        case 1:
            //Wait until OK and store mass
            if(ok)
            {
                countSet1Mass = mass;
                ok = 0;
                phase = 2;
            }
            break;
        case 2:
            //Second prompt
            //Ask user how many items are on the scales
            if(absMode == REMOTE)
            {
                redrawStatus();
                transmit(get_rom_string(_count2));
                transmit(get_rom_string(_enter));
                transmit(get_rom_string(_outro));
            }
            else
            {
                string_LCD(get_rom_string(_LCDcount2));
                if(tts_is_ready() && !mute)
                {
                    strcpy(speak_str,get_rom_string(_LCDcount2));
                    speak(speak_str);
                }
            }
            phase = 3;
        case 3:
            //Wait until OK and store value
            numInput = TRUE;
            if(ok)
            {
                numbuf[numbufi] = '\0';
                transmit(numbuf);
                countItems = atoi(numbuf);
                numInput = FALSE;
                numbufi = 0;
                ok = 0;
                phase = 4;
            }
            break;
        case 4:
            //Third prompt
            //Ask user to load any number of items to be counted.
            if(absMode == REMOTE)
            {
                redrawStatus();
                transmit(get_rom_string(_count3));
                transmit(get_rom_string(_enter));
            }
            else
            {
                string_LCD(get_rom_string(_LCDcount3));
                if(tts_is_ready() && !mute)
                {
                    strcpy(speak_str,get_rom_string(_LCDcount3));
                    speak(speak_str);
                }
            }
            phase = 5;
        case 5:
            //Wait until OK and store mass
            if(ok)
            {
                countSet2Mass = mass;
                ok = 0;
                phase = 6;
            }
            break;
        case 6:
            //Fourth prompt
            countMassPerItem = 1.0 * countSet1Mass / countItems;
            countItems2 = (int)(countSet2Mass / countMassPerItem + 0.5);
            int_to_string(countItems2,stringItems);
            if(absMode == REMOTE)
            {
                redrawStatus();
                transmit(get_rom_string(_count4));
                transmit(stringItems);
                transmit(get_rom_string(_count5));
                transmit(get_rom_string(_outro));
            }
            else
            {
                string_LCD(get_rom_string(_LCDcount4));
                string_LCD(stringItems);
                string_LCD(get_rom_string(_LCDback));
                if(tts_is_ready() && !mute)
                {
 
                    strcpy(speak_str,stringItems);
                    strcatpgm2ram(speak_str,_speakCount4);
                    speak(speak_str);
                }
            }
            phase = 7;
            break;
        case 7:
            //Wait for OK, then back to main menu
            if(ok)
            {
                ok = 0;
                func = WEIGH;
                phase = 0;
            }
            break;
    }
}